{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# PyTorch 基础 : 神经网络包nn和优化器optm\n",
    "torch.nn是专门为神经网络设计的模块化接口。nn构建于 Autograd之上，可用来定义和运行神经网络。\n",
    "这里我们主要介绍几个一些常用的类"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**约定：torch.nn 我们为了方便使用，会为他设置别名为nn,本章除nn以外还有其他的命名约定**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'1.0.0'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 首先要引入相关的包\n",
    "import torch\n",
    "# 引入torch.nn并指定别名\n",
    "import torch.nn as nn\n",
    "#打印一下版本\n",
    "torch.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "除了nn别名以外，我们还引用了nn.functional，这个包中包含了神经网络中使用的一些常用函数，这些函数的特点是，不具有可学习的参数(如ReLU，pool，DropOut等)，这些函数可以放在构造函数中，也可以不放，但是这里建议不放。\n",
    "\n",
    "一般情况下我们会**将nn.functional 设置为大写的F**，这样缩写方便调用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn.functional as F"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义一个网络\n",
    "PyTorch中已经为我们准备好了现成的网络模型，只要继承nn.Module，并实现它的forward方法，PyTorch会根据autograd，自动实现backward函数，在forward函数中可使用任何tensor支持的函数，还可以使用if、for循环、print、log等Python语法，写法和标准的Python写法一致。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Net(\n",
      "  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))\n",
      "  (fc1): Linear(in_features=1350, out_features=10, bias=True)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "class Net(nn.Module):\n",
    "    def __init__(self):\n",
    "        # nn.Module子类的函数必须在构造函数中执行父类的构造函数\n",
    "        super(Net, self).__init__()\n",
    "        \n",
    "        # 卷积层 '1'表示输入图片为单通道, '6'表示输出通道数，'5'表示卷积核为3*3\n",
    "        self.conv1 = nn.Conv2d(1, 6, 3) \n",
    "        #线性层，输入84个特征，输出10个特征\n",
    "        self.fc1   = nn.Linear(1350, 10)\n",
    "    #正向传播\n",
    "    def forward(self, x): \n",
    "        # 卷积 -> 激活 -> 池化 \n",
    "        x = self.conv1(x)\n",
    "        x = F.relu(x)\n",
    "        x = F.max_pool2d(x, (2, 2))\n",
    "        x = F.relu(x)\n",
    "        # reshape，‘-1’表示自适应\n",
    "        x = x.view(x.size()[0], -1) \n",
    "        x = self.fc1(x)        \n",
    "        return x\n",
    "\n",
    "net = Net()\n",
    "print(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "网络的可学习参数通过net.parameters()返回"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Parameter containing:\n",
      "tensor([[[[-0.2569,  0.3030, -0.2508],\n",
      "          [ 0.2527,  0.3013, -0.1692],\n",
      "          [ 0.0445,  0.0929,  0.2674]]],\n",
      "\n",
      "\n",
      "        [[[-0.1670, -0.3166,  0.3315],\n",
      "          [ 0.1171,  0.3244, -0.2757],\n",
      "          [ 0.2158, -0.3002, -0.1742]]],\n",
      "\n",
      "\n",
      "        [[[-0.1313,  0.2859,  0.1639],\n",
      "          [-0.0260,  0.2024, -0.1361],\n",
      "          [-0.1011, -0.1267,  0.2546]]],\n",
      "\n",
      "\n",
      "        [[[-0.0267,  0.2839, -0.2581],\n",
      "          [-0.2461, -0.0073,  0.3096],\n",
      "          [ 0.2565, -0.2553,  0.3148]]],\n",
      "\n",
      "\n",
      "        [[[ 0.0848, -0.3026, -0.2878],\n",
      "          [ 0.1124,  0.0947, -0.3182],\n",
      "          [-0.1941, -0.2659,  0.1424]]],\n",
      "\n",
      "\n",
      "        [[[-0.0537,  0.0605, -0.2139],\n",
      "          [ 0.2403, -0.1542,  0.0416],\n",
      "          [ 0.0483,  0.1220, -0.1305]]]], requires_grad=True)\n",
      "Parameter containing:\n",
      "tensor([ 0.3156, -0.1656, -0.0010, -0.1745,  0.0580,  0.0558], requires_grad=True)\n",
      "Parameter containing:\n",
      "tensor([[-0.0126,  0.0179,  0.0082,  ...,  0.0023,  0.0091, -0.0069],\n",
      "        [ 0.0142,  0.0079,  0.0038,  ...,  0.0081, -0.0236,  0.0226],\n",
      "        [-0.0068,  0.0093,  0.0153,  ..., -0.0085, -0.0012, -0.0122],\n",
      "        ...,\n",
      "        [-0.0071,  0.0234, -0.0262,  ..., -0.0010, -0.0241, -0.0068],\n",
      "        [ 0.0203, -0.0004,  0.0267,  ..., -0.0173,  0.0022, -0.0237],\n",
      "        [ 0.0247,  0.0200, -0.0201,  ...,  0.0081,  0.0236,  0.0140]],\n",
      "       requires_grad=True)\n",
      "Parameter containing:\n",
      "tensor([ 0.0198, -0.0144, -0.0156, -0.0073,  0.0185, -0.0177, -0.0012, -0.0125,\n",
      "        -0.0136,  0.0125], requires_grad=True)\n"
     ]
    }
   ],
   "source": [
    "for parameters in net.parameters():\n",
    "    print(parameters)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "net.named_parameters可同时返回可学习的参数及名称。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conv1.weight : torch.Size([6, 1, 3, 3])\n",
      "conv1.bias : torch.Size([6])\n",
      "fc1.weight : torch.Size([10, 1350])\n",
      "fc1.bias : torch.Size([10])\n"
     ]
    }
   ],
   "source": [
    "for name,parameters in net.named_parameters():\n",
    "    print(name,':',parameters.size())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "forward函数的输入和输出都是Tensor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 10])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "input = torch.randn(1, 1, 32, 32)\n",
    "out = net(input)\n",
    "out.size()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在反向传播前，先要将所有参数的梯度清零"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.zero_grad() \n",
    "out.backward(torch.ones(1,10)) # 反向传播的实现是PyTorch自动实现的，我们只要调用这个函数即可"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**注意**:torch.nn只支持mini-batches，不支持一次只输入一个样本，即一次必须是一个batch。\n",
    "\n",
    "也就是说，就算我们输入一个样本，也会对样本进行分批，所以，所有的输入都会增加一个维度，我们对比下刚才的input,nn中定义为3维,但是我们人工创建时多增加了一个维度，变为了4维,最前面的1即为batch-size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 损失函数\n",
    "在nn中PyTorch还预制了常用的损失函数，下面我们用MSELoss用来计算均方误差"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "29.04529571533203\n"
     ]
    }
   ],
   "source": [
    "y = torch.arange(0,10).view(1,10).float()\n",
    "criterion = nn.MSELoss()\n",
    "loss = criterion(out, y)\n",
    "#loss是个scalar，我们可以直接用item获取到他的python类型的数值\n",
    "print(loss.item()) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 优化器\n",
    "在反向传播计算完所有参数的梯度后，还需要使用优化方法来更新网络的权重和参数，例如随机梯度下降法(SGD)的更新策略如下：\n",
    "\n",
    "weight = weight - learning_rate * gradient\n",
    "\n",
    "在torch.optim中实现大多数的优化方法，例如RMSProp、Adam、SGD等，下面我们使用SGD做个简单的样例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.optim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "out = net(input)\n",
    "criterion = nn.MSELoss()\n",
    "loss = criterion(out, y)\n",
    "#新建一个优化器，SGD只需要要调整的参数和学习率\n",
    "optimizer = torch.optim.SGD(net.parameters(), lr = 0.01)\n",
    "# 先梯度清零(与net.zero_grad()效果一样)\n",
    "optimizer.zero_grad() \n",
    "loss.backward()\n",
    "\n",
    "#更新参数\n",
    "optimizer.step()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这样，神经网络的数据的一个完整的传播就已经通过PyTorch实现了，下面一章将介绍PyTorch提供的数据加载和处理工具，使用这些工具可以方便的处理所需要的数据"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Pytorch for Deeplearning",
   "language": "python",
   "name": "pytorch"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
